<template>
  <div class="uzip-wrap">
    <h2 class="uzip__title">图片压缩工具</h2>
    <div class="upload-area">
      <input
        type="file"
        accept=".jpg, .jpeg, .png"
        @change="handleFileChange"
      />
    </div>

    <div class="show-area">
      <!-- 原始图片区域 -->
      <div class="origin-file">
        <h5>原图</h5>
        <div class="img-box" v-if="originImg.src">
          <div class="img-info">
            <b>{{ originImg.name }}</b> <span>{{ originImg.size }}</span
            ><span>{{ originImg.fbl }}</span>
          </div>
          <div class="img-content">
            <img :src="originImg.src" alt="" @load="handImgLod" />
          </div>
        </div>
      </div>
      <!-- 配置区域 -->
      <div class="setting-area">
        <div class="setting-title">设置</div>
        <div class="setting-item">
          <span>图片质量:</span>
          <input
            type="range"
            min="0"
            max="100"
            step="1"
            v-model="rate"
            @change="handleRateChange"
            style="width: calc(100% - 140px)"
          />
          <span class="ml10">{{ rate }}%</span>
        </div>
        <div class="setting-item flex-c">
          <span class="download-btn" @click="downloadImg"
            >下载处理后的图片</span
          >
        </div>
      </div>
      <!-- 压缩后图片区域 -->
      <div class="origin-file">
        <h5>预览</h5>
        <div class="img-box" v-if="viewImg.src">
          <div class="img-info">
            压缩后大小:<span>{{ viewImg.size }}</span> 压缩率:<span
              :class="{
                green: viewImg.fbl > 0,
                red: viewImg.fbl < 0,
              }"
              >↓<em>{{ viewImg.fbl }}%</em></span
            >
          </div>
          <div class="img-content">
            <img :src="viewImg.src" alt="" />
          </div>
        </div>
      </div>
    </div>
    <!-- <div class="file-view-list">
      <li v-for="(item, index) in fileList" :key="index">
        <img :src="item.src" alt="" />
        <span>{{ item.name }}</span>
        <span>{{ item.size }}{{ item.unit }}</span>
      </li>
    </div> -->

    <div class="msg-tip" :class="[`${msgTip.type}`]" v-if="msgTip.show">
      {{ msgTip.text }}
    </div>

    <div v-if="isLoading" class="loading-overlay">
      <div class="loading-spinner"></div>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import UPNG from "upng-js";

// 原始图片信息
const originImg = ref({});
// 压缩后的图片信息
const viewImg = ref("");
// 压缩率
const rate = ref(80);
// 留存原始file对象
let originFile = null;
// 压缩后的file对象
let compressedFile = null;
// 消息提示
const msgTip = ref({
  show: false,
  text: "",
  type: "",
});
const showMsg = ref(false);

const isLoading = ref(false);

/**
 * 图片上传
 */
const handleFileChange = (event) => {
  let currentFiles = event.target.files;
  if (currentFiles.length) {
    let file = currentFiles[0];
    // 记录原始图片信息
    originImg.value = {
      src: URL.createObjectURL(file),
      name: file.name,
      originSize: file.size,
      size:
        file.size / 1024 > 1024
          ? (file.size / 1024 / 1024).toFixed(2) + "MB"
          : (file.size / 1024).toFixed(2) + "KB",
    };
    originFile = file;
    compressImg(file);
  }
};

/**
 * 图片压缩
 * @param {File} file - 原始图片文件
 */
const compressImg = (file) => {
  isLoading.value = true;
  checkRealMimeType(file, (realType) => {
    if (realType != "png") {
      compressImage(file, rate.value / 100).then((blob) => {
        isLoading.value = false;
        const compressedUrl = URL.createObjectURL(blob);
        const compressedSizeKB = (blob.size / 1024).toFixed(2);
        const compressionRatio = (
          (1 - blob.size / originImg.value.originSize) *
          100
        ).toFixed(2);
        viewImg.value = {
          src: compressedUrl,
          size:
            compressedSizeKB > 1024
              ? (compressedSizeKB / 1024).toFixed(2) + "MB"
              : compressedSizeKB + "KB",
          fbl: compressionRatio,
        };
      });
    } else {
      compressPNG(file).then((transferFile) => {
        isLoading.value = false;
        const compressionRatio = (
          (1 - transferFile.size / originImg.value.originSize) *
          100
        ).toFixed(2);
        const compressedSizeKB = (transferFile.size / 1024).toFixed(2);
        viewImg.value = {
          src: URL.createObjectURL(transferFile),
          size:
            compressedSizeKB > 1024
              ? (compressedSizeKB / 1024).toFixed(2) + "MB"
              : compressedSizeKB + "KB",
          fbl: compressionRatio,
        };
      });
    }
  });
};

// 检查图片真实类型
const checkRealMimeType = (file, callback) => {
  let realType = "";
  let reader = new FileReader();
  reader.readAsArrayBuffer(file);
  reader.onload = function (e) {
    let arr = new Uint8Array(e.target.result).subarray(0, 4);
    // 将截取的result和magic number比较
    let header = "";
    for (let i = 0; i < arr.length; i++) {
      header += arr[i].toString(16);
    }
    // 在这里可以得到上传图片的真实mintype类型
    // 在这里执行你的逻辑，该方法是异步执行
    if (header.indexOf("89504e47") === 0) {
      realType = "png";
    } else if (
      header.indexOf("ffd8ffe0") === 0 ||
      header.indexOf("ffd8ffe1") === 0 ||
      header.indexOf("ffd8ffe2") === 0 ||
      header.indexOf("ffd8ffe3") === 0
    ) {
      realType = "jpeg";
    } else if (header.indexOf("52494646") === 0) {
      realType = "webp";
    } else if (header.indexOf("47494638") === 0) {
      realType = "gif";
    }
    callback(realType);
  };
};

/**
 * 使用 Canvas 压缩图片
 * @param {File} file - 原始图片文件
 * @param {number} quality - 压缩质量 (0 到 1)
 * @param {number} maxWidth - 压缩后的最大宽度
 * @param {number} maxHeight - 压缩后的最大高度
 * @returns {Promise<Blob>} - 返回一个包含压缩后图片数据的 Blob 对象
 */
const compressImage = (
  file,
  quality = 0.7,
  maxWidth = 800,
  maxHeight = 800
) => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = function (event) {
      const img = new Image();
      img.onerror = () => reject(new Error("Image loading failed."));
      img.src = event.target.result;
      img.onload = function () {
        // 创建 Canvas 元素
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");

        // --- 尺寸压缩逻辑 ---
        let width = img.width;
        let height = img.height;

        // 计算缩放比例
        if (width > maxWidth || height > maxHeight) {
          const ratio = Math.min(maxWidth / width, maxHeight / height);
          width *= ratio;
          height *= ratio;
        }

        // 设置 Canvas 的最终尺寸
        canvas.width = width;
        canvas.height = height;

        // 将图片绘制到 Canvas 上（此时已完成尺寸压缩）
        ctx.drawImage(img, 0, 0, width, height);

        // --- 质量压缩逻辑 ---
        // 使用 toBlob 方法导出图片，并指定质量
        canvas.toBlob(
          function (blob) {
            if (blob) {
              resolve(blob);
            } else {
              reject(new Error("Canvas toBlob failed."));
            }
          },
          file.type,
          quality
        ); // file.type 保持原始格式，如 'image/jpeg'
      };
    };
    reader.onerror = () => reject(new Error("FileReader failed."));
    reader.readAsDataURL(file);
  });
};

/**
 * png图片压缩
 * @param {File} file - 原始图片文件
 */
const compressPNG = async (file) => {
  const arrayBuffer = await file.arrayBuffer();
  const decoded = UPNG.decode(arrayBuffer);
  const rgba8 = UPNG.toRGBA8(decoded);

  // 关键的压缩方
  // 这里 保持宽高不变，保持80%的质量（接近于 tinypng 的压缩效果）
  const compressed = UPNG.encode(
    rgba8,
    decoded.width,
    decoded.height,
    256 * (rate.value / 100)
  );
  return new File([compressed], file.name, { type: "image/png" });
};

/**
 * 原始图片加载完成后
 */
const handImgLod = (e) => {
  originImg.value.fbl = e.target.naturalWidth + "x" + e.target.naturalHeight;
};

/**
 * 图片压缩质量修改
 */
const handleRateChange = () => {
  if (originFile) {
    compressImg(originFile);
  }
};

/**
 * 下载图片
 */
const downloadImg = () => {
  if (viewImg.value.src) {
    let a = document.createElement("a");
    document.body.appendChild(a);
    a.href = viewImg.value.src;
    a.download = originImg.value.name;
    a.click();
    document.body.removeChild(a);
  } else {
    msgTip.value = {
      show: true,
      text: "请先上传图片",
      type: "warn",
    };
    setTimeout(() => {
      msgTip.value.show = false;
    }, 3000);
  }
};
</script>

<style lang="scss" scoped>
.uzip-wrap {
  position: relative;
  .msg-tip {
    position: absolute;
    z-index: 9999;
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 14px;
    color: #999;
    padding: 8px 16px;
    border-radius: 4px;
    box-shadow: 2px 2px 10px #999;
    background: #fff;
    &.warn {
      background: #e6a23c;
      color: #fff;
    }
  }
  .flex {
    display: flex;
  }
  .flex-r {
    display: flex;
    justify-content: flex-end;
  }
  .flex-c {
    display: flex;
    justify-content: center;
  }
  .ml10 {
    margin-left: 10px;
  }
  .uzip__title {
    padding: 20px 0;
    margin: 0;
    font-size: 36px;
    font-weight: bold;
  }
  .file-view-list {
    li {
      list-style: none;
      display: flex;
      align-items: center;
      img {
        width: 200px;
      }
      span {
        margin-left: 20px;
      }
    }
  }
  .upload-area {
    padding: 20px;
    border-top: 1px solid #000;
    border-bottom: 1px solid #000;
  }
  .show-area {
    display: flex;
    justify-content: center;
    margin-top: 50px;
    .origin-file {
      width: 600px;
      height: 600px;
      border: 1px solid #ccc;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;

      h5 {
        font-size: 20px;
        font-weight: bold;
        background: #f0f0f0;
        color: #000;
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        margin: 0;
      }
    }
  }
  .setting-area {
    margin: 0 20px;
    width: 500px;
    border-top: 0;
    border-bottom: 0;
    .setting-item {
      display: flex;
      margin-top: 20px;
      display: flex;

      align-items: center;
      padding-bottom: 10px;
      &:not(:last-child) {
        border-bottom: 1px solid #ccc;
      }
    }
    .setting-title {
      font-size: 16px;
      font-weight: bold;
      text-align: left;
    }
    .download-btn {
      border-radius: 4px;
      font-size: 14px;
      color: #fff;
      height: 32px;
      line-height: 32px;
      display: inline-block;
      padding: 0 15px;
      background-color: rgb(62, 153, 245);
      cursor: pointer;
      &:hover {
        background-color: rgb(100, 176, 252);
      }
    }
  }
  .img-box {
    height: calc(100% - 28px);
    margin-top: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 100%;
    .img-info {
      padding: 5px 10px;
      height: 40px;
      line-height: 40px;
      display: flex;
      align-items: center;
      position: absolute;
      top: 0;
      width: 100%;
      box-sizing: border-box;
      .green {
        color: #34a13c;
      }
      .red {
        color: red;
      }
      b {
        margin-right: 10px;
      }
      span {
        margin: 0 10px;
        color: #666;
        font-size: 14px;
        em {
          margin-left: 6px;
        }
      }
    }
    .img-content {
      width: 100%;
      height: calc(100% - 40px);
      padding: 10px;
      box-sizing: border-box;
      margin-top: 40px;
      display: flex;
      align-items: center;
      justify-content: center;
      background: conic-gradient(
          #eee 25%,
          white 0deg 50%,
          #eee 0deg 75%,
          white 0deg
        )
        0 / 20px 20px;
      img {
        max-width: 100%;
        max-height: 100%;
      }
    }
  }

  .loading-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(255, 255, 255, 0.7); /* 半透明背景 */
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 1000; /* 确保在最上层 */
  }
  .loading-spinner {
    border: 4px solid #f3f3f3; /* Light grey */
    border-top: 4px solid #3498db; /* Blue */
    border-radius: 50%;
    width: 40px;
    height: 40px;
    animation: spin 2s linear infinite;
  }
  @keyframes spin {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }
}
</style>